/*
 * 代号：凤凰
 * http://www.jphenix.org
 * 2015年10月29日
 * V4.0
 */
package com.jphenix.servlet.filter;

//#region 【引用区】
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import com.jphenix.kernel.baseobject.instanceb.ABase;
import com.jphenix.share.util.StringUtil;
import com.jphenix.standard.docs.BeanInfo;
import com.jphenix.standard.docs.ClassInfo;
import com.jphenix.standard.docs.Register;
import com.jphenix.standard.docs.Running;
import com.jphenix.standard.servlet.IFilter;
import com.jphenix.standard.servlet.IRequestManager;
import com.jphenix.standard.servlet.IResponseManager;
import com.jphenix.standard.servlet.api.IFilterConfig;
//#endregion

//#region 【说明区】
/**
 * 历史访问信息处理过滤器
 * 
 * com.jphenix.servlet.filter.HistoryUrlFilter
 * 
 * 注意：只能将模板（htm）方式的action放入历史记录中，
 * 纯action（通常由js调用，返回纯数据的）并不记录到历史中。
 * 
 * 另外只保存url中的参数，不处理post过来的数据（因为避免
 * 提交更新数据保存到历史中，随后又提交一次）
 * 
 * 执行回退的动作名为history_back.htm?key=历史分类主键&level=1  其中 level
 * 是退回层级数，默认为1（上一级）
 * 
 * 需要记录url的时候，需要在url参数中加上 _save_history_=1
 * 
 * 清除历史访问记录 _clear_history.ha
 * 
 * 将指定url路径到历史路径序列中  _set_history.ha?key=历史分类主键&url=放入的历史url路径
 * 
 * 2019-06-15 按照IFilter增加了过滤器初始化方法
 * 2020-03-13 修改了从url中获取参数
 * 2022-09-04 隔离了ServletApi，兼容新老Tomcat
 * 
 * @author 马宝刚
 * 2015年10月29日
 */
//#endregion
@ClassInfo({"2024-07-14 13:26","静态页面自动切换过滤器"})
@BeanInfo({"historyurlfilter"})
@Running({"100"})
@Register({"filtervector"})
public class HistoryUrlFilter extends ABase implements IFilter {

	//#region 【声明区】
	/**
	 * 存放在用户会话中的历史url信息容器主键
	 */
	private final String HISTORY_SESSION_KEY = "_history_url_";
	
	/**
	 * 历史记录层级主键
	 */
	private final String HISTORY_LEVEL_KEY = "_history_level";
	
	/**
	 * 提交参数中，历史记录分类主键参数名
	 */
	private final String PARAMETER_KEY = "_history_key";
	
	
	/**
	 * 主动设置指定URL到历史中，提交的参数名
	 */
	private final String PARAMETER_URL = "url";
	
	
	/**
	 * 提交参数中，历史记录层级
	 */
	private final String PARAMETER_LEVEL = "_history_level";
	
    private String     filterActionExtName = null;  // 需要过滤的扩展名
    private int        historyCount        = 10;    // 保存历史信息深度
    //#endregion

    //#region getIndex() 获取排序索引，数值越小越先执行
    /**
     * 覆盖方法
     */
    @Override
    public int getIndex() {
        return 1;
    }
    //#endregion

    //#region getFilterActionExtName() 获取需要过滤的动作路径扩展名
    /**
     * 获取需要过滤的动作路径扩展名
     * 
     * 多个扩展名用逗号分割
     * 多个扩展名用半角逗号分割
     * 扩展名前不用加半角聚号（.)
     * 如果返回空，或者空字符串，说明需要过滤全部动作路径（不建议这么做）
     * 如果需要过滤无扩展名的动作，用半角减号（-）标记
     * 
     * 注意：htm和ha并不在这里注册，因为动作类会调用 doBytesFilter方法
     * 
     * @return
     * 2015年10月29日
     * @author 马宝刚
     */
    @Override
    public String getFilterActionExtName() {
        if(filterActionExtName==null) {
            filterActionExtName = "htm";
        }
        return filterActionExtName;
    }
    //#endregion

    //#region doFilter(req,resp) 执行过滤
    /**
     * 覆盖方法
     * 刘虻
     * 2015年10月29日
     */
    @SuppressWarnings({ "unchecked", "rawtypes" })
    @Override
    public boolean doFilter(IRequestManager req, IResponseManager resp) throws Exception {
        String servletPath = str(req.getServletPath()); //动作路径
        String uri = str(req.getRequestURI()); //uri
        String queryString = str(req.getQueryString()); //提交参数
        
        if(servletPath.indexOf("/_clear_history.")>-1) {
        	req.iGetSession().setAttribute(HISTORY_SESSION_KEY,new HashMap<String,List<String>>());
        	return true;
        }
        
        if(servletPath.indexOf("/_set_history.")>-1) {
            //将指定url放入历史中
        	
        	//该动作执行后就返回页面，不在继续执行其它过滤器
        	
        	//历史记录分类主键
        	String key = str(req.getParameter(PARAMETER_KEY));
        	if(key.length()<1) {
        		key = str(req.getUrlParameter("key"));
        	}
        	if(key.length()<1) {
        		key = "_default_";
        	}
        	
        	//从会话中获取历史信息容器
            Map historyMap = (Map)req.iGetSession().getAttribute(HISTORY_SESSION_KEY);
            if(historyMap==null) {
            	historyMap = new HashMap();
            	req.iGetSession().setAttribute(HISTORY_SESSION_KEY,historyMap);
            }
            //当前分类历史信息指针
            int cLevel = sint(historyMap.get(key+HISTORY_LEVEL_KEY));
            
            //获取指定分类的历史记录
            List<String> historyList = (List<String>)historyMap.get(key);
            if(historyList==null) {
                historyList = new ArrayList<String>(historyCount);
                for(int i=0;i<historyCount;i++) {
                    historyList.add("");
                }
                historyMap.put(key,historyList);
                cLevel = -1;
            }
	        if(cLevel>=historyCount) {
	            cLevel = -1;
	        }
	        //需要放入历史记录的url   主意：在这里是可以用getParameter的，因为
	        //该过滤器处理后，不会被下一个过滤器继续处理
	        String url = String.valueOf(req.getParameter(PARAMETER_URL));
	        if(url.length()<1) {
	        	return true;
	        }
	        if(cLevel>-1) {
		        //获取上一次历史，与本次做比对，如果相同，则不放入历史
		        int tLevel = cLevel-1;
		        if(tLevel<0) {
		            tLevel = historyCount-1;
		        }
		        String lUri = historyList.get(tLevel);
		        if(lUri.length()>0 && lUri.equals(url)) {
		            //刷新了当前页面，不放入历史中
		            return true;
		        }
	        }
	        cLevel++;
	        historyList.set(cLevel,url);
	        historyMap.put(key+HISTORY_LEVEL_KEY,cLevel);
	        return true;
        }
        
        if(servletPath.indexOf("/history_back.")>-1) {
            //执行回退
        	
        	//该动作执行后就返回页面，不在继续执行其它过滤器
        	
        	//历史记录分类主键
        	String key = str(req.getParameter(PARAMETER_KEY));
        	if(key.length()<1) {
        		key = str(req.getUrlParameter("key"));
        	}
        	if(key.length()<1) {
        		key = "_default_";
        	}
        	
            //从会话中获取历史信息容器
            Map historyMap = (Map)req.iGetSession().getAttribute(HISTORY_SESSION_KEY);
            if(historyMap==null) {
            	historyMap = new HashMap();
            	req.iGetSession().setAttribute(HISTORY_SESSION_KEY,historyMap);
            }
            
            //当前历史信息指针
            //当前指针指向的位置是新的准备插入记录的位置，需要向上退回一步
            //才是上个页面
            int cLevel = sint(historyMap.get(key+HISTORY_LEVEL_KEY));
            
            //从页面提交请求中获取到的回退层级
            int level = sint(req.getParameter(PARAMETER_LEVEL));
            if(level<0) {
            	level = Math.abs(level);
            }
             cLevel-=level;
             while(cLevel<0) {
            	 cLevel = historyCount-cLevel;
             }
           
            //从会话中获取历史信息容器
            List<String> historyList = (List<String>)historyMap.get(key);
            if(historyList==null) {
                historyList = new ArrayList<String>(historyCount);
                for(int i=0;i<historyCount;i++) {
                    historyList.add("");
                }
                historyMap.put(key,historyList);
                cLevel = 0;
            }
            uri = historyList.get(cLevel);
            historyList.set(cLevel,""); //清空当前历史url，避免再次设置url时被比较为重复设置
            
            historyMap.put(key+HISTORY_LEVEL_KEY,String.valueOf(cLevel));
            
            resp.sendRedirect(uri);
            return true;
        }
        
        if(queryString.indexOf("_save_history_=1")>0){
            //放入缓存
        	
        	//主意：该过程执行后，还会进入其它过滤器继续做处理
        	//这里不能使用req.getParameter方法，因为如果是上传
        	//文件，或者提交过来的是xml活json内容，这里用了这个
        	//方法后，读入流就断了。
        	
        	
            //获取参数容器
            Map<String,String> qMap = StringUtil.fixQueryString(req.getQueryString());
            
            //历史记录分类主键
        	String key = str(qMap.get(PARAMETER_KEY));
        	if(key.length()<1) {
        		key = "_default_";
        	}
            
            //从会话中获取历史信息容器
            Map historyMap = (Map)req.iGetSession().getAttribute(HISTORY_SESSION_KEY);
            if(historyMap==null) {
            	historyMap = new HashMap();
            	req.iGetSession().setAttribute(HISTORY_SESSION_KEY,historyMap);
            }
            
            //当前历史信息指针
            int cLevel = sint(historyMap.get(key+HISTORY_LEVEL_KEY));
            
            //从会话中获取历史信息容器
            List<String> historyList = (List<String>)historyMap.get(key);
            if(historyList==null) {
                historyList = new ArrayList<String>(historyCount);
                for(int i=0;i<historyCount;i++) {
                    historyList.add("");
                }
                historyMap.put(key,historyList);
                cLevel = -1;
            }
	        if(cLevel>=historyCount) {
	            cLevel = -1;
	        }
	        if(queryString.length()>0) {
	            uri+= "?"+queryString;
	        }
	        if(cLevel>-1) {
		        //获取上一次历史，与本次做比对，如果相同，则不放入历史
		        int tLevel = cLevel-1;
		        if(tLevel<0) {
		            tLevel = historyCount-1;
		        }
		        String lUri = historyList.get(tLevel);
		        if(lUri.length()>0 && lUri.equals(uri)) {
		            //刷新了当前页面，不放入历史中
		            return false;
		        }
	        }
	        cLevel++;
	        historyList.set(cLevel,uri);
	        historyMap.put(key+HISTORY_LEVEL_KEY,cLevel);
        }
        return false;
    }
    //#endregion
    
    //#region init(bf,config) 执行初始化
	/**
	 * 执行初始化
	 * @param bf         过滤器管理类
	 * @param config     Servlet配置信息类
	 * @throws Exception 异常（如果初始化发生异常，则放弃不再使用）
	 * 2019年6月15日
	 * @author MBG
	 */
	@Override
	public void init(BaseFilter bf, IFilterConfig config) throws Exception {}
	//#endregion
	
	//#region destroy() 销毁这个过滤器
	/**
	 * 覆盖方法
	 */
	@Override
	public void destroy() {}
	//#endregion
}








